/////////////////////////////////////////////////////////////////////////
//
// GAM203
// D3DX mesh from an .x file
// MeshX.cpp
// 
// Scott Taylor
// Qantm College
//
//////////////////////////////////////////////////////////////////////////

#include "MeshX.h"

#include <d3dx9.h>
#include "Shader.h"
#include "ShaderMgr.h"
#include "Renderer.h"
#include "RenderParams.h"
#include "DXMgr.h"
#include "Util.h" // debug - remove later

MeshX::MeshX()
: m_pMesh(NULL)
, m_pShadowMesh(NULL)
{
}

MeshX::~MeshX()
{
	// Release any textures we created
	for(UINT i=0; i<m_segmentCount; ++i)
	{
		m_materials[i].second->Release();
	}
	m_materials.clear();

	// Release mesh if it was loaded
	if (m_pMesh)
	{
		m_pMesh->Release();
		m_pMesh = NULL;
	}
	if (m_pShadowMesh)
	{
		m_pShadowMesh->Release();
		m_pShadowMesh = NULL;
	}
}

//
// Sets up this object with some data
// Returns true if successful
//
bool MeshX::CreateObject(IDirect3DDevice9* a_pDevice, char* a_pTexFileName)
{
	if (!a_pDevice)
	{
		return false;
	}

	// Load the .x file
	// D3DXLoadMeshFromX returns data via these ID3DXBuffer objects
	// These allow D3DXLoadMeshFromX to return data of arbitrary length (which is what we need for an unknown number of materials etc)

	ID3DXBuffer* pMaterialBuf = NULL;
	ID3DXBuffer* pEffectBuf = NULL;

	HRESULT result = D3DXLoadMeshFromX(a_pTexFileName, D3DXMESH_DYNAMIC, a_pDevice, NULL, &pMaterialBuf, &pEffectBuf, &m_segmentCount, &m_pMesh);
	if (result != D3D_OK)
	{
		return false;
	}

	// We have to cast the buffer's data into the type we need (since the buffer is just arbitrary data)
	// In this case it is an array of D3DXMATERIAL structs, which are just a {D3DMATERIAL9, char*}
	D3DXMATERIAL* pMaterials;
	pMaterials = (D3DXMATERIAL*)pMaterialBuf->GetBufferPointer();
	// So pMaterials is now an array of D3DXMATERIALs (of size m_segmentCount)

	DWORD* pVertices = NULL;
	m_pMesh->LockVertexBuffer(D3DLOCK_READONLY, (LPVOID*)&pVertices);

	D3DXComputeBoundingBox((D3DXVECTOR3*)pVertices, m_pMesh->GetNumVertices(), D3DXGetFVFVertexSize(m_pMesh->GetFVF()), &m_aabb.GetMin(), &m_aabb.GetMax());

	// For each segmentIndex in the mesh, read its material data
	for (DWORD i = 0; i < m_segmentCount; ++i)
	{
		D3DXMATERIAL& segmentMaterialData = pMaterials[i];

		// Load the texture on this segmentIndex
		// The texture filenames don't come with any path information, filename only :(
		char texPath[256];

		sprintf_s(texPath, 256, "%s%s", "media/Meshes/",segmentMaterialData.pTextureFilename);

		// Note: We don't need to double-check NULL-termination of texPath here...
		//       MSDN on sprintf_s: sprintf_s guarantees that the buffer will be null-terminated

		// Create a new texture with this path + filename
		IDirect3DTexture9* pNewTex = NULL;

		result = D3DXCreateTextureFromFile(a_pDevice, texPath, &pNewTex);

		if (result != D3D_OK)
		{
			// Failed to load - output some debug information, but continue on
			OutputDebugString("Failed to load texture:");
			OutputDebugString(texPath);
			// pNewTex will remain NULL, which is ok (we can safely select a NULL texture when rendering)
		}

		// Store this segmentIndex's material data; we copy-by-value this D3DMATERIAL9 into a new pair
		// (If we only stored a ptr to segmentMaterialData.MatD3D, that would be dangerous because the buffer is about to be released below!)
		MaterialTexturePair newMtlTexPair(segmentMaterialData.MatD3D, pNewTex);
		m_materials.push_back(newMtlTexPair);
	}

	Renderer::GetInstance().GenerateShadowMesh(DXMgr::GetInstance().GetDevice(), m_pMesh, &m_pShadowMesh);

	// Release these since we don't need them anymore
	if (pMaterialBuf) pMaterialBuf->Release();
	if (pEffectBuf) pEffectBuf->Release();

	return true;
}

//
// Draw this object
//
void MeshX::Render(float a_dt, IDirect3DDevice9* a_pDevice, const RenderParams& a_renderParams)
{

	//Render the shadow
	if(m_pShadowMesh)
	{
		//RenderShadow(a_pDevice, a_renderParams);
	}
	if (a_pDevice && m_pMesh)
	{
		// Set up our world transform
		const char* SHADER_NAME = "AdvancedShader";

		Shader* pShader = ShaderMgr::GetInstance().Find(SHADER_NAME);
		if(pShader)
		{
			ID3DXEffect* pEffect = pShader->GetEffect();
			if(pEffect)
			{
				pEffect->SetTechnique("AdvRenderDiff1Spec0Dir1Omni1NoCol");

				pEffect->SetValue("g_fogColor", &D3DXCOLOR(0.3f, 0.35f, 0.4f, 1.0f), sizeof(D3DXCOLOR));

				pEffect->SetFloat("g_fogDistStart", 10);
				pEffect->SetFloat("g_fogDistEnd", 200);
				pEffect->SetValue("g_cameraPos", Renderer::GetInstance().GetActiveCamera()->GetEyePos(), sizeof(D3DXVECTOR3));

				//Set up lighting global variables
				for(int lightId = 0; lightId < a_renderParams.GetLightCount(); ++lightId)
				{
					if(a_renderParams.GetLight(lightId)->Type == D3DLIGHT_DIRECTIONAL)
					{
						//Set lighting parameters
						//Note: Light direction is negated to optimise the dot product in 
						//shader calculations
						pEffect->SetValue("g_directionalDir", &(D3DXVECTOR3(a_renderParams.GetLight(lightId)->Direction) * -1.0f), sizeof(D3DXVECTOR3));
						pEffect->SetValue("g_directionalDiffuse", &D3DXCOLOR(a_renderParams.GetLight(lightId)->Diffuse), sizeof(D3DXCOLOR));
						pEffect->SetValue("g_directionalSpecular", &D3DXCOLOR(a_renderParams.GetLight(lightId)->Specular), sizeof(D3DXCOLOR));
					}
					else if(a_renderParams.GetLight(lightId)->Type == D3DLIGHT_POINT)
					{
						pEffect->SetValue("g_omniPos", &D3DXVECTOR3(a_renderParams.GetLight(lightId)->Position), sizeof(D3DXVECTOR3));
						pEffect->SetFloat("g_omniRange", a_renderParams.GetLight(lightId)->Range);
						pEffect->SetValue("g_omniDiffuse", &D3DXCOLOR(a_renderParams.GetLight(lightId)->Diffuse), sizeof(D3DXCOLOR));
						pEffect->SetValue("g_omniSpecular", &D3DXCOLOR(a_renderParams.GetLight(lightId)->Specular), sizeof(D3DXCOLOR));
					}
				}
				pEffect->SetValue("g_totalAmbient", &D3DXCOLOR(a_renderParams.GetGlobalAmbient()), sizeof(D3DXCOLOR));

				//Set matrix global variables
				//Calculate a combined world*view*proj matrix for the vertex shader
				D3DXMATRIX worldViewProj = m_worldMatrix*a_renderParams.GetViewMatrix()*a_renderParams.GetProjMatrix();
				pEffect->SetMatrix("g_worldViewProj", &worldViewProj);
				pEffect->SetMatrix("g_worldMat", &m_worldMatrix);

				//Render with shader
				UINT passCount = 0;
				if(pEffect->Begin(&passCount, 0) == D3D_OK)
				{
					for(UINT passIndex = 0; passIndex < passCount; ++passIndex)
					{		
						if(pEffect->BeginPass(passIndex) == D3D_OK)
						{
							// Draw each segmentIndex of our mesh
							for (DWORD segmentIndex = 0; segmentIndex < m_segmentCount; ++segmentIndex)
							{
								//Set material global variables
								pEffect->SetValue("g_materialAmbientColor", &D3DXCOLOR(m_materials[segmentIndex].first.Ambient), sizeof(D3DXCOLOR));
								pEffect->SetValue("g_materialDiffuseColor", &D3DXCOLOR(m_materials[segmentIndex].first.Diffuse), sizeof(D3DXCOLOR));
								pEffect->SetValue("g_materialSpecularColor", &D3DXCOLOR(m_materials[segmentIndex].first.Specular), sizeof(D3DXCOLOR));
								pEffect->SetValue("g_materialEmissiveColor", &D3DXCOLOR(m_materials[segmentIndex].first.Emissive), sizeof(D3DXCOLOR));
								pEffect->SetFloat("g_materialSpecularPower", m_materials[segmentIndex].first.Power);
								pEffect->SetFloat("g_materialSpecularIntensity", 1.0f);

								//Set texture global variable
								pEffect->SetTexture("g_diffuseTexture", m_materials[segmentIndex].second);

								pEffect->CommitChanges();

								// Draw the mesh segment
								m_pMesh->DrawSubset(segmentIndex);
							}
							pEffect->EndPass();
						}
					}
					pEffect->End();
					 
				}
			}
		}
	}
}


void MeshX::RenderShadow(IDirect3DDevice9* a_pDevice, const RenderParams& a_renderParams)
{
	// Set up our world transform
	const char* SHADER_NAME = "ShadowVolumeShader";

	Shader* pShader = ShaderMgr::GetInstance().Find(SHADER_NAME);
	if(pShader)
	{
		ID3DXEffect* pEffect = pShader->GetEffect();
		if(pEffect)
		{
			pEffect->SetTechnique("RenderShadowVolume");
			//pEffect->SetTechnique("RenderScene");

			//Set up lighting global variables
			for(int lightId = 0; lightId < a_renderParams.GetLightCount(); ++lightId)
			{
				D3DXVECTOR3 pos = a_renderParams.GetLight(lightId)->Position;
				D3DCOLORVALUE col = a_renderParams.GetLight(lightId)->Diffuse;
				if(a_renderParams.GetLight(lightId)->Type == D3DLIGHT_DIRECTIONAL)
				{
					D3DXVECTOR4 light(pos.x, pos.y, pos.z, 1.0f);
					D3DXVECTOR4 lightCol(col.r, col.g, col.b, 1.0f);
					pEffect->SetVector("g_vLightView", &light);
					pEffect->SetVector("g_vLightColor", &lightCol);
				}
				else if(a_renderParams.GetLight(lightId)->Type == D3DLIGHT_POINT)
				{
					D3DXVECTOR4 light(pos.x, pos.y, pos.z, 1.0f);
					D3DXVECTOR4 lightCol(col.r, col.g, col.b, 1.0f);
					pEffect->SetVector("g_vLightView", &light);
					pEffect->SetVector("g_vLightColor", &lightCol);
				}
				D3DXVECTOR4 shadow(10.0f, 10.0f, 0.0f, 1.0f);
                pEffect->SetVector( "g_vShadowColor", &shadow);

				a_pDevice->SetVertexDeclaration(Renderer::GetInstance().GetDecl());
                    UINT cPasses;
                    pEffect->Begin( &cPasses, 0);
                    for( UINT i = 0; i < cPasses; ++i )
                    {
                        pEffect->BeginPass( i );
                        pEffect->CommitChanges();
                        m_pShadowMesh->DrawSubset( 0 );
                        pEffect->EndPass();

                    }
                    pEffect->End();
			}
		}
	}
}
